1.数据库事务
事务是用户定义的一个数据库操作系列,这些操作要么全做要么全不做,是一个不可分割的工作单位。
事务是数据库中重要的概念,通常以BEGIN TRANSACTION
开始,以COMMIT
或者ROLLBACK
结束。数据库事务具有四个著名的特性:ACID
。
Automatic
原子性:即事务中的操作要么都做,要么都不做。
Consistency
一致性:事务的执行的结果必须是使数据库从一个
一致性状态
到另一个一致性状态
。这里的一致性状态指的是物理和逻辑上的数据库一致性状态。比如一个银行系统内部各个用户直接互相转账,无论如何转账,系统内部的金额的总数总是恒定不变的。
Isolation
隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能相互干扰。
Duration
持久性:一个事务一旦提交,它对数据库中数据的改变就该是永久性的。
事务的
ACID
特性需要数据库管理系统保证,同时事务也是数据库管理系统恢复和并发控制的基本单位。
2. 数据库并发控制
2.1 数据不一致性
第1节提到了事务是并发控制的基本单位,由于并发控制可能会导致事务的ACID
特性遭到破坏,从而带来了数据库的数据不一致性。具体来讲,由于并发操作,多个事务的并发执行会带来如下四个典型的数据不一致性问题,而这几种问题主要是由于并发操作破坏了事务的隔离性(Isolation
)导致的,第3节会介绍MySQL
数据库中并发控制机制下不同的事务隔离级别。
丢失修改
这种问题是最直观,最不应该发生的,例如两个事务
T1
和T2
都读入同一数据进行修改,这样T1
提交修改结果后,随即T2
也提交了结果。这样,T2
便将T1
对数据的修改给覆盖了,这样就出现了丢失修改的问题了。这种问题发生的最主要原因是:两个或多个事务都在对同一数据项进行写操作。
不可重复读
不可重复读指的是:事务
T1
读取了某个数据项A
,在事务T1
运行期间又有其他事务例如T2
对数据项A
进行了修改并提交了,这样事务T1
再次读取数据项A
的时候,发现结果不一致了。这种问题发生的最主要原因是:一个事务在对某一个数据项进行读操作,而另外的其他事务对该数据项进行了写操作。
读脏数据
读脏数据指的是:事务
T1
正在修改某个数据项A
,事务还未提交,而事务T2
读取了数据项A
,但是随后事务T1
由于某种原因回滚了事务(并未提交事务),此时事务T2
读取到的数据项A
的值和数据库中的值是不一致的,称事务T2
读了脏数据(Read Dirty
)。这种问题发生的最主要原因是:一个事务在对某一个数据项进行写操作,而另外的其他事务对该数据项进行了读操作。
幻读
幻读指的是:事务
T1
按照某些条件从数据库中查询了一些数据记录,但是在事务T1
执行过程中,事务T2
插入或者删除了某些数据记录,这样当事务T1
再一次按照同样的条件从数据库查询的时候,发现多了或者少了某些记录。幻读本质上属于不可重复读,这里为了和第3节的隔离级别作对照,所以将其单独划出来作为幻读。产生的原因同不可重复读类似:由于事务
T1
按条件对数据表中的很多数据项查询的时候,这时候其他事务T2
对数据表进行了其他数据项的写操作(插入、修改或者删除)。为解决并发带来的一系列问题,主要有封锁、时间戳和乐观控制法来进行并发机制的设计。而封锁是最常用的数据库管理系统并发机制采用的方法。
2.2 封锁和并发度
封锁是实现并发控制的一个非常重要的技术。所谓封锁就是在事务对某个数据对象例如表、记录等进行操作的时候(读或写),先向系统请求对其加锁,成功加锁后,该事务才可以继续对其进行操作;否则,只能等待直到加锁成功。
具体来说:基本的锁分为两种:
- 排他锁(Exclusive Locks)又称写锁,X锁;
共享锁(Share Locks)又称读锁,S锁;
采用封锁确实能来保证并发下的数据库事务的特性,但是封锁带来了额外的系统开销,进而影响到系统的并发度。
例如1:当事务
T1
对某个数据项A
进行写操作的时候,为避免读脏数据, 那么所有的其他向对数据项A
进行读取的事务都得进行等待,那么系统此刻的并发度就大大降低了。例如2:为避免幻读的产生,当事务
T1
对某一表Table
中的某几行记录进项查询的时候,由于封锁了整个表Table
,此时所有其他想对该表Table
进项增加删除记录的事务都得进行等待,由于封锁的数据对象粒度太大,导致并发度大大降低。综上可以看出:封锁和并发度本来就是一种矛盾,一方面我们想要尽可能的保证事务的隔离性进而避免2.1节中的问题;另一方面我们有希望系统的并发度不要下降。
既然是矛盾,那就只能妥协,为了充分满足不同的应用场景,
MySQL
数据库InnoDB
支持4种不同的事务隔离级别。第3节将结合两种不同的封锁类型来理解这四种不同的事务隔离级别。
3. 事务隔离级别
考虑到封锁和并发度的权衡,MySQL
为用户提供了四种不同的事务隔离性级别。并发度从低到高(数据一致性强度从高到低)分别是:
- SERIALIZABLE(可串行化)可避免脏读、不可重复读、幻读情况的发生。
- REPEATABLE READ(可重复读)可避免脏读、不可重复读情况的发生。
- READ COMMITTED(读已提交)可避免脏读情况发生。
- READ UNCOMMITTED(读未提交)最低级别,以上情况均无法保证。
通过2.1节我们知道了几种数据不一致产生的主要原因,即写写和读写同时发生在多个事务对同一数据对象的情况下,另外还介绍了两种锁类型:X锁和S锁。
两个事务的X锁和S锁可以构成一个相容矩阵:如下:
T1 / T2 | X锁 | S锁 |
---|---|---|
X锁 | No | No |
S锁 | No | Yes |
含义就是:T1
对数据项加了X
锁,则T2
不能对其加S
和X
锁,T2
对数据项加了S
锁,则T1
可以对其加S
锁而不能加X
锁。(相容矩阵的加锁顺序都是是T1先加锁,T2后加锁)
- READ UNCOMMITTED(读未提交)最低级别,以上情况均无法保证。
官网给出建议:该隔离级别仅仅能做查询
The isolation level that provides the least amount of protection between transactions. Queries employ a locking strategy that allows them to proceed in situations where they would normally wait for another transaction. However, this extra performance comes at the cost of less reliable results, including data that has been changed by other transactions and not committed yet (known as dirty read). Use this isolation level with great caution, and be aware that the results might not be consistent or reproducible, depending on what other transactions are doing at the same time. Typically, transactions with this isolation level only do queries, not insert, update, or delete operations.
由于其不能保证读脏数据,因此S锁和X是相容的;因此可能如下:
T1 / T2 | X锁 | S锁 |
---|---|---|
X锁 | No(?待考证) | Yes |
S锁 | Yes | Yes |
- READ COMMITTED(读已提交)可避免脏读情况发生。
读已提交,避免脏读,但是不能保证可重复读;说明T1
的X
锁和T2
的S
锁不相容;而S
锁和X
锁相容的。也就是保证了某个事务在写数据项的时候,其他事务是不能读写该数据项的。
T1 / T2 | X锁 | S锁 |
---|---|---|
X锁 | No | No |
S锁 | Yes | Yes |
- REPEATABLE READ(可重复读)可避免脏读、不可重复读情况的发生。
可重复读,避免了脏读和不可重复读;说明T1
的X
锁和T2
的S
锁、T1
的S
锁和T2
的X
锁都不相容。也就是保证了某个事务在写某个数据项的时候,其他事务不能读写该数据项;且在读某个数据项时,其他事务只能读该数据项。
T1 / T2 | X锁 | S锁 |
---|---|---|
X锁 | No | No |
S锁 | No | Yes |
- SERIALIZABLE(可串行化)可避免脏读、不可重复读、幻读情况的发生。
为了避免幻读、不可重复读、脏读、丢失修改等产生,将并发事务的事务调度策略设置为为可串行化调度,这能保证该事务调度策略的运行结果同某一种串行调度结果一致,但是这会极大的降低并发度;同时,这也是MySQL
的InnoDB
提供的最高的事务隔离级别,这里的相容矩阵同3。
The isolation level that uses the most conservative locking strategy, to prevent any other transactions from inserting or changing data that was read by this transaction, until it is finished. This way, the same query can be run over and over within a transaction, and be certain to retrieve the same set of results each time. Any attempt to change data that was committed by another transaction since the start of the current transaction, cause the current transaction to wait.
This is the default isolation level specified by the SQL standard. In practice, this degree of strictness is rarely needed, so the default isolation level for
InnoDB
is the next most strict, REPEATABLE READ.
4. References
https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_isolation_level